package pl.mmadej.ds4java.ds.queue;

import pl.mmadej.ds4java.ds.array.ArrayDecorator;
import pl.mmadej.ds4java.ds.common.DataStructure;

import static java.lang.reflect.Array.newInstance;

public class ArrayQueue<T> extends DataStructure<T> implements Queue<T> {

    private int start;
    private int end;

    private ArrayDecorator<T> array;

    public ArrayQueue(int size) {
        array = new ArrayDecorator<T>(size + 1);
    }

    @Override
    public void enqueue(T element) {
        assertNotNull(element);
        if (shouldResizeArray()) {
            doubleArraySize();
        }
        array.setAt(end, element);
        end = increasedIndex(end);
    }

    @Override
    public T dequeue() {
        if (empty()) {
            return null;
        }
        T value = array.elementAt(start);
        array.setAt(start, null);//gc
        start = increasedIndex(start);
        return value;
    }

    @Override
    public int size() {
        return start <= end ? end - start : (array.length() - start) + end;
    }

    @Override
    public boolean empty() {
        return end == start;
    }

    @Override
    public void clear() {
        array.clear();//gc
        start = end = 0;
    }

    @Override
    @SuppressWarnings("unchecked")
    public <E> E[] toArray(E[] resultArray) {
        resultArray = createArrayIfNeeded(resultArray, size());
        return copyQueueElementsToArrayInSequence(resultArray);
    }

    private boolean shouldResizeArray() {
        return start == increasedIndex(end);
    }

    private int increasedIndex(int currentValue) {
        return (currentValue + 1) % array.length();
    }

    private void doubleArraySize() {
        Object[] newArray = copyQueueElementsToArrayInSequence(new Object[array.length() * 2]);
        array.clear();//gc
        array.setArray(newArray);
        end = array.length() / 2 - 1;
        start = 0;
    }

    private <E> E[] copyQueueElementsToArrayInSequence(E[] resultedArray) {
        if (end < start) {
            System.arraycopy(array.getArray(), start, resultedArray, 0, array.length() - start);
            System.arraycopy(array.getArray(), 0, resultedArray, array.length() - start, end);
        } else {
            System.arraycopy(array.getArray(), start, resultedArray, 0, end - start);
        }
        return resultedArray;
    }
}
